from django.db import models
from django.conf import settings

from django.utils.encoding import smart_unicode
from django.utils import simplejson
from django.core.serializers.json import DjangoJSONEncoder

from utils import get_model_collection_sets

__all__ = ["MetadataObject", "CARDINALITY", "DATA_TYPES"]

# Apparently Django doesn't really support model-level
# validation. Save this for later, or a form.
# isValidIVOID = validators.MatchesRegularExpression("ivo://[\w\d][\w\d\-_\.!~\*'\(\)\+=]{2,}(/[\w\d\-_\.!~\*'\(\)\+=]+(/[\w\d\-_\.!~\*'\(\)\+=]+)*)?")

# TODO: sort out XML IDREF. Need to know when
# to provide and IDREF in references

class MetadataObject(models.Model):
    # redudant, but explicit
    id = models.AutoField(primary_key=True)
    _collection_sets = []
    _container_field = None

    class Meta:
        abstract = True

    def to_json(self):
        return simplejson.dumps(self.to_dict(), 
                                cls=DjangoJSONEncoder,
                                ensure_ascii=False)

    def from_json(self, json_str, preserve_ids=False):
        self.from_dict(simplejson.loads(json_str), preserve_ids)

    def from_dict(self, data, preserve_ids=False):
        if preserve_ids and data.has_key('id'):
            self.id = data['id']
        for field in self._meta.fields:
            if data.has_key(field.attname) and field.serialize:
                setattr(self, field.attname, data[field.attname])
        self.save()
        for collection_name in get_model_collection_sets(self.__class__):
            if data.has_key(collection_name):
                # delete all of the existing items
                for obj in getattr(self, collection_name).all():
                    obj.delete()
                for obj_data in data[collection_name]:
                    # remove the id to the container, in case it changed
                    if obj_data.has_key("container_field"):
                        obj_data.pop(obj_data["container_field"], None)
                    obj = getattr(self, collection_name).create()
                    obj.from_dict(obj_data, preserve_ids)
        self.save()

    def to_dict(self):
        d = {'id': self.id}
        # store the container field name
        if self._container_field:
            d["container_field"] = self._container_field
        # use the serialize flag to determine which fields to dump
        for field in self._meta.fields:
            if field.serialize:
                d[field.attname] = smart_unicode(getattr(self, field.attname), 
                                              strings_only=True)
        for collection_name in get_model_collection_sets(self.__class__):
            collection = []
            for obj in getattr(self, collection_name).all():
                collection.append(obj.to_dict())
            d[collection_name] = collection
        return d

    @property
    def ivoId(self):
        return '%s#%s/%d' % (settings.SIMDB_IVO_ROOT, self.__class__.__name__, self.id)

    @property
    def xmlId(self):
        t = '%s%d' % (self.__class__.__name__, self.id)
        t = t.replace(':', '')
        return t

# selections for various fields

CARDINALITY = (("0..1", "Corresponds to 'Optional element'"), 
               ("1", "Corresponds to 'Mandatory element'"), 
               ("0..*", "Corresponds to 'Multiple but maybe Optional element'"), 
               ("1..*", "Corresponds to 'Multiple with at least one element'"))

DATA_TYPES = (("boolean", "A standard boolean type, with values 'true' and 'false', possibly represented differently in physical representations of the model."), 
              ("complex", "A standard complex type. Here no limits on accuracy of constituent Re and Im parts."), 
              ("datetime", "Represents an exact time on an exact, given date."), 
              ("double", "A real*8 floating point datatype."), 
              ("float", "A real*4 floating point datatype."), 
              ("int", "An integer*4 integer datatype."), 
              ("long", "An integer*8 integer datatype."), 
              ("rational", "A rational datatype, consisting of two integers representing the numerator and denominator."), 
              ("short", "an integer*2 integer datatype."), 
              ("string", "A standard string dattaype consisting of any type of character."))

